use halo_model::{ExtensionOperator, Metadata};
use std::collections::HashSet;
use std::hash::Hash;

use crate::extension::model::{role, user};
use macros::gvk;
use serde::{Deserialize, Serialize};


#[derive(Hash)]
#[gvk(
    group = "",
    version = "v1alpha1",
    kind = "RoleBinding",
    singular = "rolebinding",
    plural = "rolebindings"
)]
pub struct RoleBinding {
    #[serde(rename = "emailVerified")]
    pub subjects: Vec<Subject>,

    #[serde(rename = "emailVerified")]
    pub role_ref: RoleRef,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct Subject {
    #[serde(rename = "kind")]
    pub kind: String,

    #[serde(rename = "name")]
    pub name: String,

    #[serde(rename = "apiGroup")]
    pub api_group: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct RoleRef {
    #[serde(rename = "kind")]
    pub kind: String,

    #[serde(rename = "name")]
    pub name: String,

    #[serde(rename = "apiGroup")]
    pub api_group: String,
}

impl Subject {
    pub fn is_user(username: String) -> Box<dyn (Fn(&Subject) -> bool) + Send + Sync> {
        Box::new(move |subject| {
            user::User::KIND == subject.kind
                && user::User::GROUP == subject.api_group
                && username == subject.name
        })
    }

    pub fn contains_user(
        usernames: HashSet<String>,
    ) -> Box<dyn (Fn(&Subject) -> bool) + Send + Sync> {
        Box::new(move |subject| {
            let cloned_usernames = usernames.clone();
            user::User::KIND == subject.kind
                && user::User::GROUP == subject.api_group
                && cloned_usernames.contains(&subject.name)
        })
    }
    pub fn to_string(&self) -> String {
        if self.api_group.is_empty() {
            format!("{}/{}", self.kind, self.name)
        } else {
            format!("{}/{}/{}", self.api_group, self.kind, self.name)
        }
    }
}

impl RoleBinding {

    pub fn create(username: String, role_name: String) -> RoleBinding {
        let mut metadata = Metadata::new();
        metadata.with_name(format!("{}-{}-{}", username, role_name, "binding"));


        let role_ref = RoleRef {
            kind: role::Role::KIND.to_string(),
            name: role_name,
            api_group: role::GROUP.to_string(),
        };

        let subject = Subject {
            kind: user::User::KIND.to_string(),
            name: username,
            api_group: user::User::GROUP.to_string(),
        };

        let mut subjects = Vec::new();
        subjects.push(subject);
        let mut binding = RoleBinding::new();
        binding.role_ref = role_ref;
        binding.subjects = subjects;

        binding
    }

    pub fn contains_user(username: String) -> Box<dyn (Fn(&RoleBinding) -> bool) + Send + Sync> {
        Box::new(move |role_binding: &RoleBinding| {
            if role_binding
                .get_metadata()
                .get_deletion_timestamp()
                .is_none()
            {
                return role_binding
                    .subjects
                    .iter()
                    .any(Subject::is_user(username.clone()));
            }
            false
        })
    }

    fn contains(role_binding: RoleBinding, username: String) -> bool {
        if role_binding
            .get_metadata()
            .get_deletion_timestamp()
            .is_none()
        {
            return role_binding
                .subjects
                .iter()
                .any(Subject::is_user(username.clone()));
        }
        false
    }
    pub fn contains_users<'a>(
        usernames: HashSet<String>,
    ) -> Box<dyn (Fn(&RoleBinding) -> bool) + Send + Sync> {
        Box::new(move |role_binding| {
            if role_binding
                .get_metadata()
                .get_deletion_timestamp()
                .is_none()
            {
                return role_binding
                    .subjects
                    .iter()
                    .any(Subject::contains_user(usernames.clone()));
            }
            false
        })
    }
}
